<?php
/**
* DedeCMS 智能缩略图管理工具(增强版)
* 功能:
* 1. 扫描指定ID范围内的文章
* 2. 检测缩略图是否有效(三重检测)
* 3. 如果缩略图失效或为空,自动从正文提取第一张有效图片作为缩略图
* 4. 如果正文中没有有效图片,则清空缩略图字段(删除失效地址)
* 5. 支持预览模式和执行模式
*/
require_once(dirname(__FILE__) . "/include/common.inc.php");
// 绕过Dede安全检查
if (!isset($cfg_db_type)) {
$GLOBALS['safe_check'] = false;
}
// 获取参数
$startid = isset($_GET['startid']) ? intval($_GET['startid']) : 0;
$endid = isset($_GET['endid']) ? intval($_GET['endid']) : 0;
$step = isset($_GET['step']) ? intval($_GET['step']) : 50;
$timeout = isset($_GET['timeout']) ? intval($_GET['timeout']) : 5;
$min_size = isset($_GET['min_size']) ? intval($_GET['min_size']) : 256;
$mode = isset($_GET['mode']) ? $_GET['mode'] : 'preview'; // preview / execute
// HTML界面
echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>智能缩略图管理工具</title>";
echo "<style>
body{font-family:system-ui,sans-serif;padding:20px;max-width:1200px;margin:0 auto;background:#f5f7fb;}
.toolbar{background:#fff;padding:15px 20px;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,0.05);margin-bottom:20px;}
.log{background:#fff;border-radius:12px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.05);max-height:600px;overflow-y:auto;}
.log-item{font-family:monospace;font-size:13px;padding:8px 0;border-bottom:1px solid #eee;}
.success{color:#2e7d32;} .error{color:#c62828;} .warning{color:#ed6c02;} .info{color:#0d47a1;} .generated{color:#7b1fa2;} .cleared{color:#f57c00;}
button, input[type='submit']{background:#0f3b5c;color:white;border:none;padding:8px 18px;border-radius:6px;cursor:pointer;}
button:hover{background:#1e4a6e;} input[type='number']{padding:6px 10px;border:1px solid #ccc;border-radius:6px;width:100px;}
.preview-mode{background:#e3f2fd;border-left:4px solid #1976d2;padding:10px 15px;margin-bottom:15px;border-radius:6px;}
.execute-mode{background:#ffebee;border-left:4px solid #c62828;padding:10px 15px;margin-bottom:15px;border-radius:6px;}
.stats{background:#f0f4f8;padding:12px 15px;border-radius:8px;margin-top:15px;display:flex;gap:20px;flex-wrap:wrap;}
.stats-item{padding:5px 10px;border-radius:20px;background:#fff;}
</style></head><body>";
echo "<div class='toolbar'><h2>🖼️ 智能缩略图管理工具(增强版)</h2>";
echo "<p style='margin-top:-10px;color:#666;'>自动检测缩略图有效性 → 失效或为空时从正文提取第一张有效图片 → 正文无图则清空缩略图</p>";
if ($startid == 0 && $endid == 0) {
echo '<form method="get" action="">
<table style="border-collapse:collapse;">
<tr><td style="padding:5px;">起始ID:</td><td style="padding:5px;"><input type="number" name="startid" value="1" required></td>
<td style="padding:5px;">截止ID:</td><td style="padding:5px;"><input type="number" name="endid" value="5000" required></td></tr>
<tr><td style="padding:5px;">每批处理:</td><td style="padding:5px;"><input type="number" name="step" value="50"></td>
<td style="padding:5px;">超时(秒):</td><td style="padding:5px;"><input type="number" name="timeout" value="5"></td></tr>
<tr><td style="padding:5px;">最小图片(字节):</td><td style="padding:5px;"><input type="number" name="min_size" value="256"></td>
<td style="padding:5px;">模式:</td><td style="padding:5px;">
<select name="mode">
<option value="preview">🔍 预览模式(仅报告,不修改)</option>
<option value="execute">⚙️ 执行模式(自动生成或清空)</option>
</select>
</td></tr>
<tr><td colspan="4" style="padding:15px 5px;"><input type="submit" value="开始扫描处理" style="padding:10px 30px;"></td></tr>
</table>
</form>
<p style="font-size:13px;color:#666;background:#f5f5f5;padding:10px;border-radius:6px;">
📌 功能说明:<br>
1. 检测文章缩略图(litpic)是否有效(HTTP状态码+Content-Type+图片大小验证)<br>
2. 如果缩略图有效 → 保持不变<br>
3. 如果缩略图失效或为空 → 从正文(body)中提取第一张有效的图片地址<br>
4. 如果正文中有有效图片 → 设置为新的缩略图<br>
5. 如果正文中没有有效图片 → 清空缩略图字段(删除失效地址)<br>
6. 预览模式仅报告将要执行的操作,不会修改数据库
</p>';
echo "</div></body></html>";
exit;
}
$mode_name = ($mode == 'execute') ? '执行模式(自动生成或清空)' : '预览模式(仅报告)';
$mode_class = ($mode == 'execute') ? 'execute-mode' : 'preview-mode';
echo "<div class='{$mode_class}'><strong>⚙️ 当前模式:{$mode_name}</strong><br>";
if ($mode == 'preview') {
echo "📋 预览模式下仅扫描报告,不会修改数据库。确认无误后请切换为执行模式重新运行。";
} else {
echo "⚠️ 执行模式将实际修改数据库,请确保已备份!";
}
echo "</div>";
echo "<p>📌 扫描范围: ID {$startid} ~ {$endid} | 每批处理: {$step} 篇 | 超时: {$timeout}秒 | 最小有效图片: {$min_size} 字节</p>";
echo "</div><div class='log'>";
// 查询文章列表
$sql = "SELECT a.id, a.title, a.litpic, b.body
FROM `dede_archives` a
LEFT JOIN `dede_addonarticle` b ON a.id = b.aid
WHERE a.id >= {$startid} AND a.id < (" . ($startid + $step) . ") AND a.id <= {$endid}
ORDER BY a.id ASC";
$dsql->SetQuery($sql);
$dsql->Execute();
$processed = 0;
$valid_count = 0; // 缩略图有效
$invalid_count = 0; // 缩略图失效
$empty_count = 0; // 无缩略图
$generated_count = 0; // 成功从正文生成缩略图
$cleared_count = 0; // 清空缩略图(正文无图)
$failed_count = 0; // 生成失败(正文无图且清空失败)
$last_id = $startid - 1;
echo "<h3 style='margin:0 0 10px 0;'>📋 处理日志</h3>";
while ($row = $dsql->GetArray()) {
$id = $row['id'];
$title = $row['title'];
$litpic = trim($row['litpic']);
$body = $row['body'];
$last_id = $id;
$processed++;
// 状态标记
$status = '';
$new_litpic = '';
$reason = '';
// 1. 检测现有缩略图是否有效
$current_valid = false;
if (!empty($litpic)) {
$check_result = check_image_valid($litpic, $timeout, $min_size);
if ($check_result === true) {
$current_valid = true;
$valid_count++;
$status = 'valid';
echo "<div class='log-item'><span class='success'>✅ 有效</span> ID {$id} - 《" . htmlspecialchars($title) . "》<br> 缩略图: " . htmlspecialchars($litpic) . "</div>";
continue; // 缩略图有效,跳过处理
} else {
$invalid_count++;
$reason = $check_result;
$status = 'invalid';
echo "<div class='log-item'><span class='error'>❌ 失效</span> ID {$id} - 《" . htmlspecialchars($title) . "》<br> 原缩略图: " . htmlspecialchars($litpic) . "<br> 失效原因: {$reason}</div>";
}
} else {
$empty_count++;
$status = 'empty';
echo "<div class='log-item'><span class='warning'>📭 无缩略图</span> ID {$id} - 《" . htmlspecialchars($title) . "》</div>";
}
// 2. 缩略图失效或为空,尝试从正文提取有效图片
if ($status == 'invalid' || $status == 'empty') {
$extracted_url = extract_first_valid_image($body, $timeout, $min_size);
if ($extracted_url !== false) {
// 正文中有有效图片 → 设置为新的缩略图
$generated_count++;
echo "<div class='log-item'><span class='generated'>🖼️ 生成缩略图</span> ID {$id}<br> 新缩略图: " . htmlspecialchars($extracted_url) . "</div>";
if ($mode == 'execute') {
$escaped_url = addslashes($extracted_url);
$up_sql = "UPDATE `dede_archives` SET litpic='{$escaped_url}' WHERE id='{$id}'";
$dsql->ExecuteNoneQuery($up_sql);
echo "<div class='log-item' style='padding-left:20px;'><span class='success'>✅ 已更新数据库</span></div>";
} else {
echo "<div class='log-item' style='padding-left:20px;'><span class='info'>🔍 预览模式,未实际更新</span></div>";
}
} else {
// 正文中没有有效图片 → 清空缩略图字段
$cleared_count++;
echo "<div class='log-item'><span class='cleared'>🗑️ 清空缩略图</span> ID {$id} - 正文中未找到有效图片,将清空缩略图字段</div>";
if ($mode == 'execute') {
$up_sql = "UPDATE `dede_archives` SET litpic='' WHERE id='{$id}'";
$dsql->ExecuteNoneQuery($up_sql);
echo "<div class='log-item' style='padding-left:20px;'><span class='success'>✅ 已清空数据库</span></div>";
} else {
echo "<div class='log-item' style='padding-left:20px;'><span class='info'>🔍 预览模式,未实际清空</span></div>";
}
}
}
}
// 统计汇总
echo "<hr>";
echo "<div class='stats'>";
echo "<div class='stats-item'>📊 总计扫描: <strong>{$processed}</strong> 篇</div>";
echo "<div class='stats-item'>✅ 缩略图有效: <strong>{$valid_count}</strong> 篇</div>";
echo "<div class='stats-item'>❌ 缩略图失效: <strong>{$invalid_count}</strong> 篇</div>";
echo "<div class='stats-item'>📭 无缩略图: <strong>{$empty_count}</strong> 篇</div>";
echo "<div class='stats-item' style='background:#e8f0fe;'>🖼️ 从正文生成缩略图: <strong>{$generated_count}</strong> 篇</div>";
echo "<div class='stats-item' style='background:#fff3e0;'>🗑️ 清空缩略图(正文无图): <strong>{$cleared_count}</strong> 篇</div>";
echo "</div>";
if ($mode == 'preview' && ($invalid_count > 0 || $empty_count > 0)) {
echo "<div class='info' style='margin-top:15px;padding:10px;background:#e3f2fd;border-radius:6px;'>💡 提示:当前为预览模式,未实际修改数据库。确认以上操作无误后,请切换为【执行模式】重新运行。</div>";
} elseif ($mode == 'execute') {
echo "<div class='success' style='margin-top:15px;padding:10px;background:#e8f5e9;border-radius:6px;'>✅ 执行完成!共为 {$generated_count} 篇文章生成缩略图,清空 {$cleared_count} 篇失效缩略图。</div>";
}
// 自动分批继续
$next_start = $last_id + 1;
if ($next_start <= $endid && $processed > 0) {
echo "<hr><div class='info'>⏳ 已完成 ID {$startid} ~ {$last_id},正在继续下一批...</div>";
echo "<meta http-equiv='refresh' content='2;url=?startid={$next_start}&endid={$endid}&step={$step}&timeout={$timeout}&min_size={$min_size}&mode={$mode}'>";
} else {
echo "<br><a href='?'>🔙 返回首页重新开始</a>";
}
/**
* 从正文中提取第一张有效的图片地址
* @param string $body 文章正文HTML
* @param int $timeout 超时秒数
* @param int $min_size 最小有效图片大小
* @return string|false 图片地址或false
*/
function extract_first_valid_image($body, $timeout = 5, $min_size = 256) {
if (empty($body)) return false;
// 匹配所有img标签的src
preg_match_all('/<img\s+[^>]*src=[\'"]([^\'"]+)[\'"][^>]*>/i', $body, $matches);
if (empty($matches[1])) return false;
foreach ($matches[1] as $url) {
$url = trim($url);
if (empty($url)) continue;
// 验证图片有效性
$check = check_image_valid($url, $timeout, $min_size);
if ($check === true) {
return $url;
}
}
return false;
}
/**
* 三重检测:状态码 + Content-Type + 真实图片格式/最小大小
* @param string $url 图片地址
* @param int $timeout 超时秒数
* @param int $min_size 最小有效图片大小(字节)
* @return bool|string true=有效, 其他=失效原因
*/
function check_image_valid($url, $timeout = 5, $min_size = 256) {
$url = trim($url);
if (empty($url)) return '空地址';
// 本地文件检测
if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
$local_path = $_SERVER['DOCUMENT_ROOT'] . '/' . ltrim($url, '/');
if (!file_exists($local_path)) return '本地文件不存在';
$size = filesize($local_path);
if ($size < $min_size) return "图片过小({$size}字节)";
$info = @getimagesize($local_path);
if ($info === false) return '本地文件非图片格式';
return true;
}
// 远程URL检测
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$error = curl_errno($ch);
curl_close($ch);
if ($error !== 0) return "连接失败(error:{$error})";
if ($http_code != 200) return "HTTP状态码:{$http_code}";
if (strpos($content_type, 'image/') !== 0) return "非图片类型:{$content_type}";
// 下载一小部分数据验证图片格式和大小
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RANGE, "0-16383");
$data = curl_exec($ch);
$download_size = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD);
curl_close($ch);
if ($data === false || $download_size == 0) return "无法获取图片数据";
if ($download_size < $min_size) return "图片过小({$download_size}字节)";
$info = @getimagesizefromstring($data);
if ($info === false) return "非有效图片格式";
return true;
}
echo "</div></body></html>";
?>